/* -*- java -*- */
/*----------------------------------------------------------------------
Compiler Generator CocoXml/R,
Copyright (c) 2008 Charles Wang <charlesw123456@gmail.com>

This program is free software; you can redistribute it and/or modify it 
under the terms of the GNU General Public License as published by the 
Free Software Foundation; either version 2, or (at your option) any 
later version.

This program is distributed in the hope that it will be useful, but 
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License 
for more details.

You should have received a copy of the GNU General Public License along 
with this program; if not, write to the Free Software Foundation, Inc., 
59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

As an exception, it is allowed to write an extension of CocoXml/R that is
used as a plugin in non-free software.

If not otherwise stated, any source code generated by CocoXml/R (other than 
CocoXml/R itself) does not fall under the GNU General Public License.
-----------------------------------------------------------------------*/
/*---- Begin ----*/
import java.lang.RuntimeException;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.parsers.SAXParser;
import java.io.IOException;
import java.io.InputStream;
import java.io.FileInputStream;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import java.util.Queue;
import java.util.Stack;

/*---- Namespace ----*/

class Token {
/*---- Options ----*/
    public int kind;
    public int col;
    public int line;
    public String val;
}

class TagInfo {
    public int startToken;
    public int endToken;
}

class XmlLangDefinition {
    public boolean[]                  useVector;
    public Hashtable<String, TagInfo> Tags;
    public Hashtable<String, Integer> Attrs;
    public Hashtable<String, Integer> PInstructions;

    public XmlLangDefinition() {
	useVector = new boolean[Token.numOptions];
	Tags = new Hashtable<String, TagInfo>();
	Attrs = new Hashtable<String, Integer>();
	PInstructions = new Hashtable<String, Integer>();
    }

    public void AddTag(String Name, int StartToken, int EndToken) {
	TagInfo tinfo = new TagInfo();
	tinfo.startToken = StartToken;
	tinfo.endToken = EndToken;
	Tags.put(Name, tinfo);
    }

    public void AddAttr(String Name, int AttrToken) {
	Attrs.put(Name, AttrToken);
    }

    public void AddProcessingInstruction(String PIName, int PIToken) {
	PInstructions.put(PIName, PIToken);
    }
}

enum SAXRecordType {
    StartElement, EndElement, Attribute,
    Text, Whitespace, Comment, ProcessingInstruction
}

class SAXRecord {
    SAXRecordType  typ;
    String         namespaceURI;
    String         localName;
    String         value;
}

class XSHandler extends DefaultHandler {
    Queue<SAXRecord> queue;

    public XSHandler() {
	queue = new ArrayDeque<SAXRecord>();
    }

    public void startElement(String namespaceURI, String localName,
			     String QName, Attributes attrs)
	throws SAXException
    {
	String nsURI;
	SAXRecord record = new SAXRecord();
	record.typ = SAXRecordType.StartElement;
	record.namespaceURI = namespaceURI;
	record.localName = localName;
	queue.add(record);
	for (int idx = 0; idx < attrs.getLength(); ++idx) {
	    record = new SAXRecord();
	    record.typ = SAXRecordType.Attribute;
	    nsURI = attrs.getURI(idx);
	    record.namespaceURI = (nsURI == "") ? namespaceURI : nsURI;
	    record.localName = attrs.getLocalName(idx);
	    record.value = attrs.getValue(idx);
	    queue.add(record);
	}
    }

    public void endElement(String namespaceURI, String localName, String QName)
	throws SAXException
    {
	SAXRecord record = new SAXRecord();
	record.typ = SAXRecordType.EndElement;
	record.namespaceURI = namespaceURI;
	record.localName = localName;
	queue.add(record);
    }

    public void characters(char[] ch, int start, int length) {
	SAXRecord record = new SAXRecord();
	record.value = new String(ch, start, length);
	record.typ = (record.value.trim().length() == 0) ?
	    SAXRecordType.Whitespace : SAXRecordType.Text;
	queue.add(record);
    }

    public void ignorableWhitespace(char[] ch, int start, int length) {
	SAXRecord record = new SAXRecord();
	record.typ = SAXRecordType.Whitespace;
	record.value = new String(ch, start, length);
	queue.add(record);
    }

    // Comment is not supported by basic SAX API...

    public void processingInstruction(String target, String data) {
	SAXRecord record = new SAXRecord();
	record.typ = SAXRecordType.ProcessingInstruction;
	record.localName = target;
	record.value = data;
	queue.add(record);
    }

    public SAXRecord get() {
	return queue.poll();
    }
}

class tagStackEle {
    public boolean unknownNamespace;
    public boolean unknownTag;
    public XmlLangDefinition xldef;
}

public class XmlScanner {
    static final char EOL = '\n';
    static final int eofSym = 0;
/*---- Declarations ----*/

    InputStream                           stream;
    XSHandler                             handler;
    Hashtable<String, XmlLangDefinition>  XmlLangMap;
    Stack<tagStackEle>                    tagStack;
    List<Token>                           tokens;
    int                                   peek;

    public XmlScanner (String fileName) {
	try {
	    stream = new FileInputStream(fileName);
	} catch (IOException ioe) {
	    throw new RuntimeException("Cannot open " + fileName + ".");
	}
	Init();
    }

    public XmlScanner (InputStream s) {
	stream = s;
	Init();
    }

    void Init() {
	SAXParserFactory spf;
	SAXParser saxParser = null;
	XmlLangDefinition curXLDef;

	spf = SAXParserFactory.newInstance();
	spf.setNamespaceAware(true);
	try {
	    saxParser = spf.newSAXParser();
	} catch (Exception ex) {
	    System.err.println(ex);
	    System.exit(1);
	}

	handler = new XSHandler();
	try {
	    saxParser.parse(stream, handler);
	} catch (SAXException se) {
	    System.err.println(se.getMessage());
	    System.exit(1);
	} catch (IOException ioe) {
	    System.err.println(ioe);
	    System.exit(1);
	}

	XmlLangMap = new Hashtable<String, XmlLangDefinition>();
	tagStack = new Stack<tagStackEle>();
	tokens = new ArrayList<Token>();
	peek = 0;

/*---- Initialization ----*/

	tagStackEle  tsEle = new tagStackEle();
	tsEle.unknownNamespace = false;
	tsEle.unknownTag = false;
	tsEle.xldef = XmlLangMap.get("");
	tagStack.push(tsEle);
    }

    int StartElement2Kind(String NamespaceURI, String TagName) {
	tagStackEle curEle = tagStack.peek();
	tagStackEle newEle = new tagStackEle();

	if (!XmlLangMap.containsKey(NamespaceURI)) {
	    newEle.unknownNamespace = true;
	    newEle.unknownTag = false;
	    newEle.xldef = curEle.xldef;
	    tagStack.push(newEle);
	    return useKindVector[Token.Options.UNKNOWN_NAMESPACE.ordinal()];
	}
	newEle.unknownNamespace = false;
	newEle.xldef = XmlLangMap.get(NamespaceURI);
	if (!newEle.xldef.Tags.containsKey(TagName)) {
	    newEle.unknownTag = true;
	    tagStack.push(newEle);
	    return useKindVector[Token.Options.UNKNOWN_TAG.ordinal()];
	}
	newEle.unknownTag = false;
	tagStack.push(newEle);
	return newEle.xldef.Tags.get(TagName).startToken;
    }

    int EndElement2Kind(String NamespaceURI, String TagName) {
	tagStackEle curEle = tagStack.pop();

	if (curEle.unknownNamespace)
	    return useKindVector[Token.Options.END_UNKNOWN_NAMESPACE.ordinal()];
	if (curEle.unknownTag)
	    return useKindVector[Token.Options.END_UNKNOWN_TAG.ordinal()];
	return curEle.xldef.Tags.get(TagName).endToken;
    }

    int Attribute2Kind(String NamespaceURI, String AttrName) {
	XmlLangDefinition xldef;
	//tagStackEle curEle = tagStack.peek();
	//assert(!curEle.unknownNamespace and !curEle.unknownTag);
	if (!XmlLangMap.containsKey(NamespaceURI))
	    return useKindVector[Token.Options.UNKNOWN_ATTR_NAMESPACE.ordinal()];
	xldef = XmlLangMap.get(NamespaceURI);
	if (!xldef.Attrs.containsKey(AttrName))
	    return useKindVector[Token.Options.UNKNOWN_ATTR.ordinal()];
	return xldef.Attrs.get(AttrName);
    }

    int ProcessingInstruction2Kind(String PIName) {
	tagStackEle curEle = tagStack.peek();
	// Should I always use 'XmlLangMap.get("")' but not 'curEle.xldef' here? 
	if (!curEle.xldef.PInstructions.containsKey(PIName))
	    return useKindVector[Token.Options.UNKNOWN_PROCESSING_INSTRUCTION.ordinal()];
	return curEle.xldef.PInstructions.get(PIName);
    }

    int Others2Kind(SAXRecordType rtype) {
	Token.Options opt;
	tagStackEle curEle = tagStack.peek();

	switch (rtype) {
	case Text:
	    if (curEle.unknownNamespace) opt = Token.Options.UNS_TEXT;
	    else if (curEle.unknownTag) opt = Token.Options.UT_TEXT;
	    else opt = Token.Options.TEXT;
	    break;
	case Whitespace:
	    if (curEle.unknownNamespace) opt = Token.Options.UNS_WHITESPACE;
	    else if (curEle.unknownTag) opt = Token.Options.UT_WHITESPACE;
	    else opt = Token.Options.WHITESPACE;
	    break;
	case Comment:
	    if (curEle.unknownNamespace) opt = Token.Options.UNS_COMMENT;
	    else if (curEle.unknownTag) opt = Token.Options.UT_COMMENT;
	    else opt = Token.Options.COMMENT;
	    break;
	default:
	    return -1;
	}
	int iopt = opt.ordinal();
	return curEle.xldef.useVector[iopt] ? useKindVector[iopt] : -1;
    }

    void AppendToken() {
	int kind = -1;
	Token new_token = null;
	SAXRecord record = null;
	while (kind < 0) {
	    record = handler.get();
	    if (record == null) { kind = eofSym; break; }
	    switch (record.typ) {
	    case StartElement:
		kind = StartElement2Kind(record.namespaceURI,
					 record.localName);
		break;
	    case EndElement:
		kind = EndElement2Kind(record.namespaceURI,
				       record.localName);
		break;
	    case Attribute:
		kind = Attribute2Kind(record.namespaceURI,
				      record.localName);
		break;
	    case ProcessingInstruction:
		kind = ProcessingInstruction2Kind(record.localName);
		break;
	    default:
		kind = Others2Kind(record.typ);
		break;
	    }
	}
	new_token = new Token();
	new_token.kind = kind;
	new_token.line = -1; // Unsupported by basic SAX API.
	new_token.col = -1; // Unsupported by basic SAX API.
	new_token.val = (kind == eofSym) ? null : record.value;
	tokens.add(new_token);
    }

    public Token Scan () {
	Token token;
	if (tokens.isEmpty()) AppendToken();
	token = tokens.get(0);
	tokens.remove(0);
	return token;
    }

    public Token Peek () {
	if (peek == tokens.size()) AppendToken();
	return tokens.get(peek ++);
    }

    public void ResetPeek () { peek = 0; }
}

/*---- $$$ ----*/
